/*
 *   Copyright (C) 2009  Jens Kieselbach
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You may obtain a copy of the License at http://www.gnu.org/licenses/gpl.html
 */
package de.kieselbach.grootional.component

import de.kieselbach.grootional.util.*
import de.kieselbach.grootional.value.*
import de.kieselbach.sql.*
import de.kieselbach.swing.*
import de.kieselbach.swing.border.*
import java.sql.*
import java.util.logging.*
import javax.swing.*
import javax.swing.tree.*

import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension

public class InfoBrowserCard extends Card {

    def uiResources
    def resourceBundle

    def connection

    def schemasAndCatalogs = [] as List
    def schemaAndCatalog
    def selectedType
    def selectedTable
    def selectedColumn

    def refreshButton
    def collapseButton
    def selectButton

    def infos
    def model

    def selectionListeners = [] as Set

    InfoBrowserCard(def uires, def con) {

        super(new BorderLayout())
        processable = true

        uiResources = uires
        resourceBundle = uiResources.resourceBundles['gui']

        connection = con
        schemaAndCatalog = new CatalogSchemaValue(con.connectionValue.catalog, con.connectionValue.schema)

        // -----------------------------------------------------

        title = resourceBundle.getString('browserCard.title')
        icon = uiResources.images['dbinfo16'] as Icon

        refreshButton = new FlatButton(uiResources.images['refresh16'])
        refreshButton.enabled = false
        refreshButton.toolTipText = resourceBundle.getString('browserCard.refresh')
        toolBarItems << refreshButton
        refreshButton.actionPerformed = {
            refresh()
        }

        toolBarItems << Box.createHorizontalGlue()

        collapseButton = new FlatButton(uiResources.images['collapseall16'])
        collapseButton.enabled = false
        collapseButton.toolTipText = resourceBundle.getString('browserCard.collapseall')
        toolBarItems << collapseButton
        collapseButton.actionPerformed = {
            def row = infos.rowCount - 1
            while (row > 0) {
                infos.collapseRow(row);
                row--;
            }
        }

        selectButton = new FlatButton(uiResources.images['dropdown16'])
        selectButton.enabled = false
        selectButton.toolTipText = resourceBundle.getString('browserCard.catalogandschema')
        toolBarItems << selectButton
        selectButton.actionPerformed = {
            selectCatalohAndSchema()
        }

        model = new DefaultTreeModel(null)
        infos = new JTree(model)
        infos.selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION

        def renderer = new DatabaseTreeCellRenderer(infos.cellRenderer)
        infos.cellRenderer = renderer

        add(new JScrollPane(infos), BorderLayout.CENTER)

        renderer.tableIcon = uiResources.images['table16']
        renderer.schemaIcon = uiResources.images['schema16']
        renderer.typeUnknownIcon = uiResources.images['type_unknown16']
        renderer.typeTableIcon = uiResources.images['type_table16']
        renderer.typeSystemIcon = uiResources.images['type_system16']
        renderer.typeViewIcon = uiResources.images['type_view16']

        infos.valueChanged = {event ->

            selectedType = null
            selectedTable = null
            selectedColumn = null

            def node = infos.getLastSelectedPathComponent()
            if(node != null) {
                selectedType = node.tableType
                selectedTable = node.tableName
                if(node.type != null && node.type == DatabaseNode.COLUMN) {
                    selectedColumn = node.name
                }
            }

            if(selectionListeners.size() > 0 && selectedType != null && selectedTable != null) {
                selectionListeners.each() {l ->
                    l(schemaAndCatalog, selectedType, selectedTable, selectedColumn)
                }
            }
        }

        // -----------------------------------------------------------------

        def img = uiResources.images['column_plain'].getImage()
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.ARRAY)] = new TypeIcon(img, 'ARR')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BIGINT)] = new TypeIcon(img, 'BGI')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BINARY)] = new TypeIcon(img, 'BIN')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BIT)] = new TypeIcon(img, 'BIT')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BLOB)] = new TypeIcon(img, 'BLB')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BOOLEAN)] = new TypeIcon(img, 'BOL')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.CHAR)] = new TypeIcon(img, 'CHR')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.CLOB)] = new TypeIcon(img, 'CLB')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DATALINK)] = new TypeIcon(img, 'DLK')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DATE)] = new TypeIcon(img, 'DAT')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DECIMAL)] = new TypeIcon(img, 'DEC')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DISTINCT)] = new TypeIcon(img, 'DCT')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DOUBLE)] = new TypeIcon(img, 'DOU')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.FLOAT)] = new TypeIcon(img, 'FLO')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.INTEGER)] = new TypeIcon(img, 'INT')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.JAVA_OBJECT)] = new TypeIcon(img, 'JOB')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.LONGNVARCHAR )] = new TypeIcon(img, 'LNC')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.LONGVARBINARY)] = new TypeIcon(img, 'LVB')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.LONGVARCHAR)] = new TypeIcon(img, 'LVC')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NCHAR)] = new TypeIcon(img, 'NCH')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NCLOB)] = new TypeIcon(img, 'NBL')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NULL)] = new TypeIcon(img, 'NUL')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NUMERIC)] = new TypeIcon(img, 'NUM')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NVARCHAR)] = new TypeIcon(img, 'NVC')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.OTHER)] = new TypeIcon(img, '?')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.REAL)] = new TypeIcon(img, 'REA')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.REF)] = new TypeIcon(img, 'REF')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.ROWID)] = new TypeIcon(img, 'RID')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.SMALLINT)] = new TypeIcon(img, 'SIT')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.SQLXML)] = new TypeIcon(img, 'XML')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.STRUCT)] = new TypeIcon(img, 'STT')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.TIME)] = new TypeIcon(img, 'TIM')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.TIMESTAMP)] = new TypeIcon(img, 'TSP')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.TINYINT)] = new TypeIcon(img, 'TIT')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.VARBINARY)] = new TypeIcon(img, 'VBI')
        renderer.columnIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.VARCHAR)] = new TypeIcon(img, 'VCH')

        img = uiResources.images['column_pk_plain'].getImage()
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.ARRAY)] = new TypeIcon(img, 'ARR')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BIGINT)] = new TypeIcon(img, 'BGI')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BINARY)] = new TypeIcon(img, 'BIN')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BIT)] = new TypeIcon(img, 'BIT')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BLOB)] = new TypeIcon(img, 'BLB')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.BOOLEAN)] = new TypeIcon(img, 'BOL')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.CHAR)] = new TypeIcon(img, 'CHR')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.CLOB)] = new TypeIcon(img, 'CLB')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DATALINK)] = new TypeIcon(img, 'DLK')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DATE)] = new TypeIcon(img, 'DAT')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DECIMAL)] = new TypeIcon(img, 'DEC')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DISTINCT)] = new TypeIcon(img, 'DCT')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.DOUBLE)] = new TypeIcon(img, 'DOU')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.FLOAT)] = new TypeIcon(img, 'FLO')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.INTEGER)] = new TypeIcon(img, 'INT')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.JAVA_OBJECT)] = new TypeIcon(img, 'JOB')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.LONGNVARCHAR )] = new TypeIcon(img, 'LNC')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.LONGVARBINARY)] = new TypeIcon(img, 'LVB')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.LONGVARCHAR)] = new TypeIcon(img, 'LVC')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NCHAR)] = new TypeIcon(img, 'NCH')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NCLOB)] = new TypeIcon(img, 'NBL')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NULL)] = new TypeIcon(img, 'NUL')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NUMERIC)] = new TypeIcon(img, 'NUM')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.NVARCHAR)] = new TypeIcon(img, 'NVC')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.OTHER)] = new TypeIcon(img, '?')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.REAL)] = new TypeIcon(img, 'REA')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.REF)] = new TypeIcon(img, 'REF')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.ROWID)] = new TypeIcon(img, 'RID')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.SMALLINT)] = new TypeIcon(img, 'SIT')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.SQLXML)] = new TypeIcon(img, 'XML')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.STRUCT)] = new TypeIcon(img, 'STT')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.TIME)] = new TypeIcon(img, 'TIM')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.TIMESTAMP)] = new TypeIcon(img, 'TSP')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.TINYINT)] = new TypeIcon(img, 'TIT')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.VARBINARY)] = new TypeIcon(img, 'VBI')
        renderer.columnPkIcons[DatabaseTreeCellRenderer.getDataTypIndex(Types.VARCHAR)] = new TypeIcon(img, 'VCH')

        // -----------------------------------------------------------------

        refresh()
    }

    void refresh() {

        schemasAndCatalogs.clear()
        model.root = null

        refreshButton.enabled = false
        collapseButton.enabled = false
        selectButton.enabled = false

        validate()
        repaint()

        startProcessing()

        Thread.start() {

            def root = null
            def res = new SqlResources()
            try {
                def con = res << connection.getConnection()

                def meta = con.getMetaData()
                def schemas = res << meta.getSchemas()
                def resmeta = schemas.getMetaData()
                def full = resmeta.getColumnCount() >= 2
                while (schemas.next()) {
                    def catalog = null
                    def schema = schemas.getString(1)
                    if (full) {
                        catalog = schemas.getString(2)
                    }

                    schemasAndCatalogs << new CatalogSchemaValue(catalog, schema)
                }

                root = new DatabaseNode(DatabaseNode.ROOT, connection.connectionValue.description)

                if(schemaAndCatalog.toString() != null && schemaAndCatalog.toString().length() > 0) {
                    root.add(new DatabaseNode(DatabaseNode.SCHEMA, schemaAndCatalog.toString(), false))
                }

                // --------------------------------------------------------------------------------

                def typennodes = [:] as TreeMap
                def types = res << meta.getTableTypes()


                new SqlResult(res << meta.getTableTypes(),[new StringField(1)]).each() {type ->
                    def desc = null
                    def nt = DatabaseNode.parseTableType(type)
                    switch(nt) {
                        case DatabaseNode.TYPE_TABLE:
                            desc = resourceBundle.getString('browserCard.typeTable')
                            break

                        case DatabaseNode.TYPE_SYSTEM:
                            desc = resourceBundle.getString('browserCard.typeSystem')
                            break

                        case DatabaseNode.TYPE_VIEW:
                            desc = resourceBundle.getString('browserCard.typeView')
                            break

                        case DatabaseNode.TYPE_SYNONYM:
                            desc = resourceBundle.getString('browserCard.typeSynonym')
                            break
                    }

                    def tnode = new DatabaseNode(nt, type, desc)
                    typennodes[type] = tnode
                }

                // --------------------------------------------------------------------------------

                def tablenodes = [:] as Map
                def primarykeys = [] as Set
                new SqlResult(res << meta.getTables(schemaAndCatalog.catalog, schemaAndCatalog.schema, null, null),
                    [new StringField(3), new StringField(4)]).each() {
                        table, type ->

                    def tablenode = new DatabaseNode(DatabaseNode.TABLE, table)
                    tablenodes[table] = tablenode
                    typennodes[type].add(tablenode)

                    tablenode.tableType = type
                    tablenode.tableName = table

                    new SqlResult(res << meta.getPrimaryKeys(schemaAndCatalog.catalog, schemaAndCatalog.schema, table),
                        [new StringField(4)]).each() {
                            pkfield ->

                        primarykeys << (table + '_' + pkfield)
                    }
                }

                // --------------------------------------------------------------------------------

                new SqlResult(res << meta.getColumns(schemaAndCatalog.catalog, schemaAndCatalog.schema, null, null),
                    [new StringField(3), new StringField(4), new IntegerField(5)]).each() {
                        table, column, datatype ->

                    def colnode = new DatabaseNode(DatabaseNode.COLUMN, column, false)
                    colnode.dataType = datatype
                    tablenodes[table].add(colnode)

                    colnode.tableType = tablenodes[table].tableType
                    colnode.tableName = table

                    colnode.primaryKey = primarykeys.contains(table + '_' + column)
                }

                typennodes.each() {key, val ->
                    if(val.childCount > 0) {
                        root.add(val)
                    }
                }
            }
            catch(Exception exc) {
                Logger.getLogger(getClass().name).log(Level.WARNING, 'Could not receive database meta data!', exc)
                root = null
            }
            finally {
                res.close()
            }

            SwingUtilities.invokeLater {

                stopProcessing()

                refreshButton.enabled = true
                collapseButton.enabled = true
                selectButton.enabled = schemasAndCatalogs.size() > 0

                model.root = root

                validate()
                repaint()
            }
        }
    }

    void selectCatalohAndSchema() {

        if(schemasAndCatalogs != null && schemasAndCatalogs.size() > 0) {

            def popup = new JPopupMenu()

            int count = 0
            def parent = popup
            def blank = uiResources.images['blank16']

            schemasAndCatalogs.each() {val ->

                count++
                if(count > 30) {
                    count = 0
                    JMenu m = new JMenu(resourceBundle.getString('browserCard.more'))
                    parent.add(m)
                    parent = m
                }

                def item = new JMenuItem(val.toString(), val == schemaAndCatalog ? uiResources.images['ok16'] : blank)
                parent.add(item)
                item.actionPerformed = {
                    schemaAndCatalog = val
                    refresh()
                }
            }

            popup.show(selectButton, selectButton.width - popup.preferredSize.@width, selectButton.height)
        }
    }
}